
搜索文档...
文档首页
核心文档
快速起步
CLI 快速起步
开发指南
API参考手册
附加文档
教程
高级文档
动画
属性型指令
浏览器支持
组件样式
部署
多级注入器
响应式表单
HTTP 客户端
生命周期钩子
Angular模块 (NgModule)
npm 包
管道
路由与导航
安全
搭建剖析
结构型指令
测试
TypeScript 配置
从 AngularJS 升级
Webpack 简介
烹饪宝典
Angular for TypeScript
管道
管道可以在模板中转换显示的内容。
每个应用开始的时候差不多都是一些简单任务：获取数据、转换它们，然后把它们显示给用户。 获取数据可能简单到创建一个局部变量就行，也可能复杂到从WebSocket中获取数据流。

一旦取到数据，我们可以把它们原始值的toString结果直接推入视图中。 但这种做法很少能具备良好的用户体验。 比如，几乎每个人都更喜欢简单的日期格式，例如1988-04-15，而不是服务端传过来的原始字符串格式 —— Fri Apr 15 1988 00:00:00 GMT-0700 (Pacific Daylight Time)。

显然，有些值最好显示成用户友好的格式。我们很快就会发现，在很多不同的应用中，都在重复做出某些相同的变换。 我们几乎会把它们看做某种CSS样式，事实上，我们也确实更喜欢在HTML模板中应用它们 —— 就像CSS样式一样。

通过引入Angular管道，我们可以把这种简单的“显示-值”转换器声明在HTML中。

试试在线例子 / 可下载的例子。

使用管道

管道把数据作为输入，然后转换它，给出期望的输出。 我们将把组件的birthday属性转换成对人类更友好的日期格式，来说明这一点：

src/app/hero-birthday1.component.ts
Copy Code
import { Component } from '@angular/core';

@Component({
  selector: 'hero-birthday',
  template: `<p>The hero's birthday is {{ birthday | date }}</p>`
})
export class HeroBirthdayComponent {
  birthday = new Date(1988, 3, 15); // April 15, 1988
}
重点看下组件的模板。

Copy Code
<p>The hero's birthday is {{ birthday | date }}</p>
在这个插值表达式中，我们让组件的birthday值通过管道操作符( | )流动到 右侧的Date管道函数中。所有管道都会用这种方式工作。

Date和Currency管道需要ECMAScript国际化（I18n）API，但Safari和其它老式浏览器不支持它，该问题可以用垫片（Polyfill）解决。
Copy Code
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Intl.~locale.en"></script>
内置的管道

Angular内置了一些管道，比如DatePipe、UpperCasePipe、LowerCasePipe、CurrencyPipe和PercentPipe。 它们全都可以直接用在任何模板中。

要学习更多内置管道的知识，参见API参考手册，并用“pipe”为关键词对结果进行过滤。
Angular没有FilterPipe或OrderByPipe管道，原因在后面的附录中有解释。
对管道进行参数化

管道可能接受任何数量的可选参数来对它的输出进行微调。 我们可以在管道名后面添加一个冒号( : )再跟一个参数值，来为管道添加参数(比如currency:'EUR')。 如果我们的管道可以接受多个参数，那么就用冒号来分隔这些参数值(比如slice:1:5)。

我们将通过修改生日模板来给这个日期管道提供一个格式化参数。 当格式化完该英雄的4月15日生日之后，它应该被渲染成04/15/88。

Copy Code
<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }} </p>
参数值可以是任何有效的模板表达式（参见模板语法中的模板表达式部分），比如字符串字面量或组件的属性。 换句话说，借助属性绑定，我们也可以像用绑定来控制生日的值一样，控制生日的显示格式。

我们来写第二个组件，它把管道的格式参数绑定到该组件的format属性。这里是新组件的模板：

src/app/hero-birthday2.component.ts (template)
Copy Code
template: `
  <p>The hero's birthday is {{ birthday | date:format }}</p>
  <button (click)="toggleFormat()">Toggle Format</button>
`
我们还能在模板中添加一个按钮，并把它的点击事件绑定到组件的toggleFormat()方法。 此方法会在短日期格式('shortDate')和长日期格式('fullDate')之间切换组件的format属性。

src/app/hero-birthday2.component.ts (class)
Copy Code
export class HeroBirthday2Component {
  birthday = new Date(1988, 3, 15); // April 15, 1988
  toggle = true; // start with true == shortDate

  get format()   { return this.toggle ? 'shortDate' : 'fullDate'; }
  toggleFormat() { this.toggle = !this.toggle; }
}
当我们点击按钮的时候，显示的日志会在“04/15/1988”和“Friday, April 15, 1988”之间切换。

Date Format Toggle
要了解更多DatePipes的格式选项，请参阅API文档。
链式管道

我们可以把管道链在一起，以组合出一些潜在的有用功能。 下面这个例子中，我们把birthday链到DatePipe管道，然后又链到UpperCasePipe，这样我们就可以把生日显示成大写形式了。 比如下面的代码就会把生日显示成APR 15, 1988：

Copy Code
The chained hero's birthday is
{{ birthday | date | uppercase}}
下面这个显示FRIDAY, APRIL 15, 1988的例子用同样的方式链接了这两个管道，而且同时还给date管道传进去一个参数。

Copy Code
The chained hero's birthday is
{{  birthday | date:'fullDate' | uppercase}}
自定义管道

我们还可以写自己的自定义管道。 下面就是一个名叫ExponentialStrengthPipe的管道，它可以放大英雄的能力：

src/app/exponential-strength.pipe.ts
Copy Code
import { Pipe, PipeTransform } from '@angular/core';
/*
 * Raise the value exponentially
 * Takes an exponent argument that defaults to 1.
 * Usage:
 *   value | exponentialStrength:exponent
 * Example:
 *   {{ 2 |  exponentialStrength:10}}
 *   formats to: 1024
*/
@Pipe({name: 'exponentialStrength'})
export class ExponentialStrengthPipe implements PipeTransform {
  transform(value: number, exponent: string): number {
    let exp = parseFloat(exponent);
    return Math.pow(value, isNaN(exp) ? 1 : exp);
  }
}
在这个管道的定义中体现了几个关键点：

管道是一个带有“管道元数据(pipe metadata)”装饰器的类。
这个管道类实现了PipeTransform接口的transform方法，该方法接受一个输入值和一些可选参数，并返回转换后的值。
当每个输入值被传给transform方法时，还会带上另一个参数，比如我们这个管道中的exponent(放大指数)。
我们通过@Pipe装饰器告诉Angular：这是一个管道。该装饰器是从Angular的core库中引入的。
这个@Pipe装饰器允许我们定义管道的名字，这个名字会被用在模板表达式中。它必须是一个有效的JavaScript标识符。 比如，我们这个管道的名字是exponentialStrength。
PipeTransform接口
transform方法是管道的基本要素。 PipeTransform接口中定义了它，并用它指导各种工具和编译器。 理论上说，它是可选的。Angular不会管它，而是直接查找并执行transform方法。
现在，我们需要一个组件来演示这个管道。

src/app/power-booster.component.ts
Copy Code
import { Component } from '@angular/core';

@Component({
  selector: 'power-booster',
  template: `
    <h2>Power Booster</h2>
    <p>Super power boost: {{2 | exponentialStrength: 10}}</p>
  `
})
export class PowerBoosterComponent { }
Power Booster
要注意的有两点：

我们使用自定义管道的方式和内置管道完全相同。
我们必须在AppModule的declarations数组中包含这个管道。
别忘了`declarations`数组
我们必须手动注册自定义管道。如果忘了，Angular就会报告一个错误。 在前一个例子中我们没有把DatePipe列进去，这是因为Angular所有的内置管道都已经预注册过了。
如果我们试一下这个在线例子 / 可下载的例子，就可以通过修改值和模板中的可选部分来体会其行为。

能力倍增计算器

仅仅升级模板来测试这个自定义管道其实没多大意思。 我们干脆把这个例子升级为“能力倍增计算器”，它可以把该管道和使用ngModel的双向数据绑定组合起来。

src/app/power-boost-calculator.component.ts
Copy Code
import { Component } from '@angular/core';
@Component({
  selector: 'power-boost-calculator',
  template: `
    <h2>Power Boost Calculator</h2>
    <div>Normal power: <input [(ngModel)]="power"></div>
    <div>Boost factor: <input [(ngModel)]="factor"></div>
    <p>
      Super Hero Power: {{power | exponentialStrength: factor}}
    </p>
  `
})
export class PowerBoostCalculatorComponent {
  power = 5;
  factor = 1;
}
Power Boost Calculator
管道与变更检测

Angular通过变更检测过程来查找绑定值的更改，并在每一次JavaScript事件之后运行：每次按键、鼠标移动、定时器以及服务器的响应。 这可能会让变更检测显得很昂贵，但是Angular会尽可能降低变更检测的成本。

当我们使用管道时，Angular选用了一种简单、快速的变更检测算法。

无管道
我们下一个例子中的组件使用默认的、激进(昂贵)的变更检测策略来检测和更新heroes数组中的每个英雄。下面是它的模板：

src/app/flying-heroes.component.html (v1)
Copy Code
New hero:
  <input type="text" #box
          (keyup.enter)="addHero(box.value); box.value=''"
          placeholder="hero name">
  <button (click)="reset()">Reset</button>
  <div *ngFor="let hero of heroes">
    {{hero.name}}
  </div>
和模板相伴的组件类可以提供英雄数组，能把新的英雄添加到数组中，还能重置英雄数组。

src/app/flying-heroes.component.ts (v1)
Copy Code
export class FlyingHeroesComponent {
  heroes: any[] = [];
  canFly = true;
  constructor() { this.reset(); }

  addHero(name: string) {
    name = name.trim();
    if (!name) { return; }
    let hero = {name, canFly: this.canFly};
    this.heroes.push(hero);
  }

  reset() { this.heroes = HEROES.slice(); }
}
我们可以添加新的英雄，加完之后，Angular就会更新显示。 reset按钮会把heroes替换成一个由原来的英雄组成的新数组，重置完之后，Angular就会更新显示。 如果我们提供了删除或修改英雄的能力，Angular也会检测到那些更改，并更新显示。

“会飞的英雄”管道
我们来往*ngFor重复器中添加一个FlyingHeroesPipe管道，这个管道能过滤出所有会飞的英雄。

src/app/flying-heroes.component.html (flyers)
Copy Code
<div *ngFor="let hero of (heroes | flyingHeroes)">
  {{hero.name}}
</div>
下面是FlyingHeroesPipe的实现，它遵循了我们以前见过的那些写自定义管道的模式。

src/app/flying-heroes.pipe.ts
Copy Code
import { Pipe, PipeTransform } from '@angular/core';

import { Flyer } from './heroes';

@Pipe({ name: 'flyingHeroes' })
export class FlyingHeroesPipe implements PipeTransform {
  transform(allHeroes: Flyer[]) {
    return allHeroes.filter(hero => hero.canFly);
  }
}
当运行在线例子 / 可下载的例子时，我们看到一种奇怪的行为。添加的每个英雄都是会飞行的英雄，但是没有一个被显示出来。

虽然我们没有得到期望的行为，但Angular也没有出错。 这里只是用了另一种变更检测算法 —— 它会忽略对列表及其子项所做的任何更改。

来看看我们是如何添加新英雄的：

Copy Code
this.heroes.push(hero);
当我们往heroes数组中添加一个新的英雄时，这个数组的引用并没有改变。它还是那个数组。而引用却是Angular所关心的一切。 从Angular的角度来看，这是同一个数组，没有变化，也就不需要更新显示。

我们可以修复它。让我们创建一个新数组，把这个英雄追加进去，并把它赋给heroes。 这次，Angular检测到数组的引用变化了。它执行了这个管道，并使用这个新数组更新显示，这次它就包括新的飞行英雄了。

如果我们修改了这个数组，没有管道被执行，也没有显示被更新。 如果我们替换了这个数组，管道就会被执行，显示也更新了。 这个飞行英雄的例子用检查框和其它显示内容扩展了原有代码，来帮我们体验这些效果。

Flying Heroes
直接替换这个数组是通知Angular更新显示的一种高效方式。 我们该什么时候替换这个数组呢？当数据变化的时候。 在这个玩具级例子中，这是一个简单的规则，因为这里修改数据的唯一途径就是添加新英雄。

更多情况下，我们不知道什么时候数据变化了，尤其是在那些有很多种途径改动数据的程序中 —— 可能在程序中很远的地方。 组件就是一个通常无法知道那些改动的例子。此外，它会导致削足适履 —— 扭曲我们的组件设计来适应管道。 我们要尽可能保持组件类独立于HTML。组件不应该关心管道的存在。

为了过滤会飞的英雄，我们要使用非纯(impure)管道。

纯(pure)管道与非纯(impure)管道

有两类管道：纯的与非纯的。 默认情况下，管道都是纯的。我们以前见到的每个管道都是纯的。 通过把它的pure标志设置为false，我们可以制作一个非纯管道。我们可以像这样让FlyingHeroesPipe变成非纯的：

Copy Code
@Pipe({
  name: 'flyingHeroesImpure',
  pure: false
})
在继续往下走之前，我们先理解一下纯和非纯之间的区别，从纯管道开始。

纯管道
Angular只有在它检测到输入值发生了纯变更时才会执行纯管道。 纯变更是指对原始类型值(String、Number、Boolean、Symbol)的更改， 或者对对象引用(Date、Array、Function、Object)的更改。

Angular会忽略(复合)对象内部的更改。 如果我们更改了输入日期(Date)中的月份、往一个输入数组(Array)中添加新值或者更新了一个输入对象(Object)的属性，Angular都不会调用纯管道。

这可能看起来是一种限制，但它保证了速度。 对象引用的检查是非常快的(比递归的深检查要快得多)，所以Angular可以快速的决定是否应该跳过管道执行和视图更新。

因此，如果我们要和变更检测策略打交道，就会更喜欢用纯管道。 如果不能，我们就可以转回到非纯管道。

或者我们也可以完全不用管道。 有时候，使用组件的属性能比用管道更好的达到目的，这一点我们等后面会再提起。
非纯管道
Angular会在每个组件的变更检测周期中执行非纯管道。 非纯管道可能会被调用很多次，和每个按键或每次鼠标移动一样频繁。

要在脑子里绷着这根弦，我们必须小心翼翼的实现非纯管道。 一个昂贵、迟钝的管道将摧毁用户体验。

非纯版本的FlyingHeroesPipe
我们把FlyingHeroesPipe换成了FlyingHeroesImpurePipe。 下面是完整的实现：

FlyingHeroesImpurePipe FlyingHeroesPipe
Copy Code
@Pipe({
  name: 'flyingHeroesImpure',
  pure: false
})
export class FlyingHeroesImpurePipe extends FlyingHeroesPipe {}
我们把它从FlyingHeroesPipe中继承下来，以证明无需改动内部代码。 唯一的区别是管道元数据中的pure标志。

这是一个很好地非纯管道候选者，因为它的transform函数又小又快。

Copy Code
return allHeroes.filter(hero => hero.canFly);
我们可以从FlyingHeroesComponent派生出一个FlyingHeroesImpureComponent。

src/app/flying-heroes-impure.component.html (excerpt)
Copy Code
<div *ngFor="let hero of (heroes | flyingHeroesImpure)">
  {{hero.name}}
</div>
唯一的重大改动就是管道。 我们可以在在线例子 / 可下载的例子中确认，当我们输入新的英雄甚至修改

数组时，这个

的显示也跟着更新了。

非纯AsyncPipe
Angular的AsyncPipe是一个有趣的非纯管道的例子。 AsyncPipe接受一个Promise或Observable作为输入，并且自动订阅这个输入，最终返回它们给出的值。

AsyncPipe管道是有状态的。 该管道维护着一个所输入的Observable的订阅，并且持续从那个Observable中发出新到的值。

在下面例子中，我们使用该async管道把一个消息字符串(message$)的Observable绑定到视图中。

src/app/hero-async-message.component.ts
Copy Code
import { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/take';
@Component({
  selector: 'hero-message',
  template: `
    <h2>Async Hero Message and AsyncPipe</h2>
    <p>Message: {{ message$ | async }}</p>
    <button (click)="resend()">Resend</button>`,
})
export class HeroAsyncMessageComponent {
  message$: Observable<string>;
  private messages = [
    'You are my hero!',
    'You are the best hero!',
    'Will you be my hero?'
  ];
  constructor() { this.resend(); }
  resend() {
    this.message$ = Observable.interval(500)
      .map(i => this.messages[i])
      .take(this.messages.length);
  }
}
这个Async管道节省了组件的样板代码。 组件不用订阅这个异步数据源，而且不用在被销毁时取消订阅(如果订阅了而忘了反订阅容易导致隐晦的内存泄露)。

一个非纯而且带缓存的管道
我们来写更多的非纯管道：一个向服务器发起HTTP请求的管道。

时刻记住，非纯管道可能每隔几微秒就会被调用一次。 如果我们不小心点，这个管道就会发起一大堆请求“攻击”服务器。

我们确实得小心点。 这个管道只有当所请求的URL发生变化时才会向服务器发起请求。它会缓存服务器的响应。 代码如下，它使用Angular http客户端来接收数据

src/app/fetch-json.pipe.ts
Copy Code
import { Pipe, PipeTransform } from '@angular/core';
import { Http }                from '@angular/http';
import 'rxjs/add/operator/map';
@Pipe({
  name: 'fetch',
  pure: false
})
export class FetchJsonPipe  implements PipeTransform {
  private cachedData: any = null;
  private cachedUrl = '';
  constructor(private http: Http) { }
  transform(url: string): any {
    if (url !== this.cachedUrl) {
      this.cachedData = null;
      this.cachedUrl = url;
      this.http.get(url)
        .map( result => result.json() )
        .subscribe( result => this.cachedData = result );
    }
    return this.cachedData;
  }
}
接下来我们用一个测试台组件演示一下它，该组件的模板中定义了两个使用到此管道的绑定，他们都从heroes.json文件中取得英雄数据。

src/app/hero-list.component.ts
Copy Code
import { Component } from '@angular/core';
@Component({
  selector: 'hero-list',
  template: `
    <h2>Heroes from JSON File</h2>
    <div *ngFor="let hero of ('heroes.json' | fetch) ">
      {{hero.name}}
    </div>
    <p>Heroes as JSON:
      {{'heroes.json' | fetch | json}}
    </p>`
})
export class HeroListComponent { }
组件渲染起来是这样的：

Hero List
这个管道上的断点请求数据的过程显示：

每个绑定都有它自己的管道实例。
每个管道实例都缓存了它自己的URL和数据。
每个管道实例都只调用一次服务器。
JsonPipe
第二个绑定除了用到FetchPipe之外还链接了更多管道。 我们把获取数据的结果同时显示在第一个绑定和第二个绑定中。第二个绑定中，我们通过链接到一个内置管道JsonPipe把它转成了JSON格式。

借助json管道进行调试
JsonPipe为你诊断数据绑定的某些神秘错误或为做进一步绑定而探查数据时，提供了一个简单途径。
纯管道与纯函数
纯管道使用纯函数。 纯函数是指在处理输入并返回结果时，不会产生任何副作用的函数。 给定相同的输入，它们总是返回相同的输出。

我们在本章前面见过的管道都是用纯函数实现的。 内置的DatePipe就是一个用纯函数实现的纯管道。 ExponentialStrengthPipe是如此， FlyingHeroesComponent也是如此。 不久前我们刚看过的FlyingHeroesImpurePipe，是一个用纯函数实现的非纯管道。

但是一个纯管道必须总是用纯函数实现。忽略这个警告将导致失败并带来一大堆这样的控制台错误：表达式在被检查后被变更。

下一步

管道能很好的封装和共享的通用“值-显示”转换逻辑。我们可以像样式一样使用它们，把它们扔到模板表达式中，以提升视图的表现力和可用性。

要浏览Angular的所有内置管道，请到API参考手册。 学着写写自定义管道，并贡献给开发社区。

附录：没有FilterPipe或者OrderByPipe

Angular没有随身发布过滤或列表排序的管道。 熟悉AngularJS的开发人员应该知道filter和orderBy过滤器，但在Angular中它们没有等价物。

这并不是疏忽。Angular不想提供这些管道，因为 (a) 它们性能堪忧，以及 (b) 它们会阻止比较激进的代码最小化(minification)。 无论是filter还是orderBy都需要它的参数引用对象型属性。 我们前面学过，这样的管道必然是非纯管道，并且Angular会在几乎每一次变更检测周期中调用非纯管道。

过滤、 特别是排序是昂贵的操作。 当Angular每秒调用很多次这类管道函数时，即使是中等规模的列表都可能严重降低用户体验。 在AngularJS程序中，filter和orderBy经常被误用，结果连累到Angular自身，人们抱怨说它太慢。 从某种意义上，这也不冤：谁叫AngularJS把filter和orderBy作为首发队员呢？是它自己准备了这个性能陷阱。

虽然不是很明显，但代码最小化方面也存在风险。想象一个用于英雄列表的排序管道。我们可能根据英雄原始属性中的name和planet进行排序，就像这样：

Copy Code
<!-- NOT REAL CODE! -->
<div *ngFor="let hero of heroes | orderBy:'name,planet'"></div>
我们使用文本字符串来标记出排序字段，期望管道通过索引形式(如hero['name'])引用属性的值。 不幸的是，激进的代码最小化策略会改变Hero类的属性名，所以Hero.name和Hero.planet可能会被变成Hero.a和Hero.b。 显然，hero['name']是无法正常工作的。

我们中的一些人可能不想做那么激进的最小化。但那不过是我们的选择而已。 Angular作为一个产品不应该拒绝那些想做激进的最小化的人。 所以，Angular开发组决定随Angular一起发布的每样东西，都应该能被安全的最小化。

Angular开发组和一些有经验的Angular开发者强烈建议你：把你的过滤和排序逻辑挪进组件本身。 组件可以对外暴露一个filteredHeroes或sortedHeroes属性，这样它就获得控制权，以决定要用什么频度去执行其它辅助逻辑。 你原本准备实现为管道，并在整个应用中共享的那些功能，都能被改写为一个过滤/排序的服务，并注入到组件中。

如果你不需要顾虑这些性能和最小化问题，也可以创建自己的管道来实现这些功能(参考FlyingHeroesPipe中的写法)或到社区中去找找。

资源库

关于
书籍与培训
工具与库
社区
宣传资料
帮助

Stack Overflow
Gitter
Google Group
报告问题
网站反馈
社区

会议
Meetups
Twitter
GitHub
做贡献
其它语种

英文版
Powered by Google ©2010-2017。代码授权方式：MIT-style License。文档授权方式：CC BY 4.0。
本网站由洛阳永欣维护  豫ICP备16019859号-1